package com.lzy.redis.impl;

import com.lzy.redis.RedisService;
import com.lzy.util.ArrayUtil;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.util.CollectionUtils;

import java.io.Serializable;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * redis server封装
 *
 * @author lzy by 2019/09/12
 */
public class RedisServiceImpl implements RedisService {

    private final static String RELEASE_DISTRIBUTED_LOCK_SCRIPT = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";

    private final static String RELEASE_SET_IF_SCRIPT = "if redis.call('exists',KEYS[1]) == 0 then redis.call('set',KEYS[1],ARGV[1]) redis.call('expire',KEYS[1],ARGV[2]) return 1 else return 0 end";

    private final static String RELEAS_INCREMENT_INIT = "if not redis.call('get',KEYS[1]) then redis.call('incrby',KEYS[1],ARGV[1]) redis.call('expire',KEYS[1],ARGV[3]) return redis.call('incrby',KEYS[1],ARGV[2]) else return redis.call('incrby',KEYS[1],ARGV[2]) end";

    private RedisTemplate<Object, Object> redisTemplate;

    @Override
    public void set(final String key, final Object value) {
        redisTemplate.opsForValue().set(key, value);
    }

    @Override
    public Set<String> getRedisKeys(String key) {
        Set<Object> set = redisTemplate.keys(key);
        if (CollectionUtils.isEmpty(set)) {
            return new HashSet<>();
        }
        return set.stream().map(String::valueOf).collect(Collectors.toSet());
    }

    @Override
    public Boolean setnx(String key, Object value) {
        return redisTemplate.opsForValue().setIfAbsent(key, value);
    }

    @Override
    public Boolean setnx(String key, Object value, Long liveSeconds) {
        DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
        redisScript.setScriptText(RELEASE_SET_IF_SCRIPT);
        redisScript.setResultType(Long.class);
        Long execute = redisTemplate.execute(redisScript, Collections.singletonList(key), value, liveSeconds);
        return Objects.equals(execute, 1L);
    }

    @Override
    public Object get(final String key) {
        return redisTemplate.opsForValue().get(key);
    }

    @Override
    public void delete(final String key) {
        redisTemplate.delete(key);
    }

    @Override
    public void set(final String key, final Object value, final Long liveSeconds) {
        redisTemplate.opsForValue().set(key, value, liveSeconds, TimeUnit.SECONDS);
    }

    @Override
    public Long getExpire(String key) {
        return redisTemplate.getExpire(key);
    }

    @Override
    public Boolean expire(String key, Long liveSeconds) {
        return redisTemplate.expire(key, liveSeconds, TimeUnit.SECONDS);
    }

    @Override
    public Boolean hasKey(String key) {
        return redisTemplate.hasKey(key);
    }

    @Override
    public void setToHash(String key, Object hashKey, Object value) {
        redisTemplate.opsForHash().put(key, String.valueOf(hashKey), value);
    }

    @Override
    public void putAllHash(String key, Map<String, Object> values) {
        redisTemplate.opsForHash().putAll(key, values);
    }

    @Override
    public Object getFromHash(String key, Object hashKey) {
        return redisTemplate.opsForHash().get(key, String.valueOf(hashKey));
    }

    @Override
    public Map<Object, Object> getHashToMap(String key) {
        return redisTemplate.opsForHash().entries(key);
    }

    @Override
    public Boolean hasHashKey(String key, Object hashKey) {
        return redisTemplate.opsForHash().hasKey(key, String.valueOf(hashKey));
    }

    @Override
    public Set<Object> hashKeys(String key) {
        return redisTemplate.opsForHash().keys(key);
    }

    @Override
    public Long hashSize(String key) {
        return redisTemplate.opsForHash().size(key);
    }

    @Override
    public void setToSet(String key, Object value) {
        redisTemplate.opsForSet().add(key, value);
    }

    @Override
    public void deleteFromHash(String key, Object... hashKeys) {
        if (ArrayUtil.isEmpty(hashKeys)) {
            return;
        }
        String[] array = Stream.of(hashKeys).map(String::valueOf).toArray(String[]::new);
        redisTemplate.opsForHash().delete(key, array);
    }

    @Override
    public Long increment(String key, Long value) {
        return redisTemplate.boundValueOps(key).increment(value);
    }

    @Override
    public Long increment(String key) {
        return redisTemplate.boundValueOps(key).increment(1L);
    }

    @Override
    public Long increment(String key, Long value, Long initValue, Long liveSeconds) {
        DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
        redisScript.setScriptText(RELEAS_INCREMENT_INIT);
        redisScript.setResultType(Long.class);
        return redisTemplate.execute(redisScript, Collections.singletonList(key), initValue, value, liveSeconds);
    }

    @Override
    public void sendMessage(String channel, Serializable message) {
        redisTemplate.convertAndSend(channel, message);
    }


    @Override
    public void rightPutAllList(String key, Collection objs) {
        redisTemplate.opsForList().rightPushAll(key, objs);
    }


    @Override
    public void leftPutAllList(String key, Collection objs) {
        redisTemplate.opsForList().leftPushAll(key, objs);
    }

    @Override
    public List listLeftPop(String key, int count) {
        return redisTemplate.executePipelined((RedisCallback<Object>) connection -> {
            for (int i = 0; i < count; i++) {
                connection.lPop(key.getBytes());
            }
            return null;
        }).stream()
                .filter(Objects::nonNull)
                .collect(Collectors.toList());

    }

    @Override
    public Long listSize(String key) {
        Long size = redisTemplate.opsForList().size(key);
        if (size == null){
            return 0L;
        }
        return size;
    }


    /**
     * 释放分布式锁
     *
     * @param lockKey   锁
     * @param requestId 请求标识
     * @return 是否释放成功
     */
    @Override
    public boolean releaseDistributedLock(String lockKey, String requestId) {
        DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
        redisScript.setScriptText(RELEASE_DISTRIBUTED_LOCK_SCRIPT);
        redisScript.setResultType(Long.class);
        Long execute = redisTemplate.execute(redisScript, Collections.singletonList(lockKey), requestId);
        return Objects.equals(execute, 1L);
    }

    public RedisServiceImpl() {
    }

    public RedisServiceImpl(RedisTemplate<Object, Object> redisTemplate) {
        this.redisTemplate = redisTemplate;
    }
}
